<?php
/**
 *
 *
 */
abstract class Yz_Item_Abstract
    extends Yz_Object
    implements Yz_Item_Interface
{


    /** the model the item belongs to
     *  @var Yz_Model_Interface
     */
    protected $model;


    /** the item of parent model
     *  @var Yz_Model_Interface
     */
    protected $parentItem;


    /************************************
     *          Public methods          *
     ************************************/

    /**
     *  @param Yz_Model_Interface $model
     *  @return ignored
     */
    public function __construct(Yz_Model_Interface $model, $values=null, $prefix=null)
    {
        $this->model = $model;
        $this->__dict__ = $model->toArray();
        if ($values)
            $this->assignValues($values, $prefix);
    }


    /** On cloning, force removal of the item's id
     *  @return ignored
     */
    public function __clone()
    {
        $this->__dict__['id'] = null;
    }


    /** Return the item's string representation
     *  @return string
     */
    public function __toString()
    {
        $id = $this->id;
        $name = $this->className();
        if ($id)
            $name .= ' #' . $id;
        else
            $name .= '(new)';
        return $name;
    }


    public function current()
    {
        $key = key($this->__dict__);
        return $this->_realGet($key);
    }

   /**
     *  check whether the item is a valid item of the model
     *      This holds when:
     *      - either the supplied model object is identical to the item's one
     *      - or the item's model is a subclass of the the supplied model's class
     *  @param Yz_Model_Interface $model
     *  @return bool
     */
    final public function isItemOf( Yz_Model_Interface $model )
    {
        return $model === $this->model || $this->model instanceof $model;
    }

    /** describe an item, i.e. get its model
     *  @return Yz_Model_Interface $model
     */
    final public function describe()
    {
        return $this->model;
    }

    /**
     *  get item's values as hash
     *  @return array
     *  @todo must modify the method to allow custom getters to be used
     */
    public function toArray()
    {
        $values = array();
        foreach ($this->__dict__ as $key => $value) {
            $values[$key] = $this->_realGet($key);
        }
        return $values;
    }

    /**
     *  get item's title from its 'title' or 'name' value, or from its tostring method
     *  @return string
     *  @todo must modify the method to allow custom getters to be used
     */
    public function getTitle()
    {
        if (isset($this->__dict__['title']))
            $title = $this->_realGet('title');
        else if (isset($this->__dict__['name']))
            $title = $this->_realGet('name');
        else
            $title = $this->__toString();
        return $title;
    }

    /**
     *  save item to the model
     *      Use model's update/add depending on whether the ID is empty
     *  @return this
     */
    public function fetchNeighbors()
    {
        $id = $this->_realGet('id');
        if (! $id)
            throw new Yz_Exception('No neighbors without id');
        return $this->model->fetchNeighbors($this);
    }

    /**
     *  save item to the model
     *      Use model's update/add depending on whether the ID is empty
     *  @return this
     */
    public function save()
    {
        $id = $this->_realGet('id');
        if ($id) {
            $this->model->updateItem($this);
        } else {
            $this->__dict__['id'] = $this->model->addItem($this);
        }
        return $this;
    }

    /**
     *  delete item from the model
     *  @return this
     */
    public function delete()
    {
        $id = $this->_realGet('id');
        if (! $id)
            throw new Yz_Exception('Can not delete item without id');
        return $this->model->deleteItem($this);
    }


    /*******************************
     *      Protected methods      *
     *******************************/

    protected function assignValues( $values, $prefix )
    {
        debug();
        foreach ($this->model as $propName => $prop) {
            $this->__dict__[$propName] = $prop->assignValue($this->model, $propName, $values, $prefix);
        }
        $parentModel = $this->model->getParentModel();
        if ($parentModel) {
            $this->parentItem = $this->__dict__['_'.$parentModel->getName()];
            $this->__dict__ = $this->parentItem->__dict__ + $this->__dict__;
        }
        debug("Created item id={$this->__dict__['id']}");
        return $this;
    }

    /** test the value being set, using the property's testValue()
     *  @return this
     *  @throw InaccessibleProperty
     */
    protected function defaultGetter( $var )
    {
        if (! array_key_exists($var, $this->__dict__))
            $this->undefinedVariable( $var, 'get' );
        return $this->_realGet($var);
    }

    /** test the value being set, using the property's testValue()
     *  @return this
     *  @throw InaccessibleProperty
     */
    protected function defaultSetter( $var, $value )
    {
        try {
            $prop = $this->model[$var];
            $this->__dict__[$var] = $prop->testValue($value);
        } catch (Yz_Exception_InaccessibleProperty $ex) {
            $this->undefinedVariable( $var, 'set' );
        } catch (Yz_Exception $ex) {
            throw Yz_Item_Exception_InvalidArgument::_new(array('property' => $var));
        }
        return $this;
    }

    /** forbid unsetting variables
     *  @throw InaccessibleProperty
     */
    protected function defaultUnsetter( $var )
    {
        return $this->undefinedVariable( $var, 'set' );
    }


    protected function _realGet( $var )
    {
        $value =& $this->__dict__[$var];
        try {
            // if value is a property object, it is deemed uninitialized,
            // use property's selectValue to initialize
            if ($value instanceof Yz_Property_Interface)
                $value = $value->selectValue($var, $this);
            #if ($value instanceof Yz_Property_Interface)
            #    throw Yz_Property_Exception::_new('Bad return value');
        } catch (Yz_Property_Exception $ex) {
            // property exists but failed to yield value
            debug($ex);
            $this->undefinedVariable( $var, 'get' );
        }
        return $value;
    }

}
